//=============================================================================
//                                                                            
//   Exercise code for the lecture
//                                                                            
//=============================================================================
//=============================================================================
//
//  CLASS TerrainViewer - IMPLEMENTATION
//
//=============================================================================

//== INCLUDES =================================================================

#include "TerrainViewer.h"
#include "../../utils/Mesh3DReader.h"

//== IMPLEMENTATION ========================================================== 

//=========================================================================================
// Perlin Noise Data
//=========================================================================================

// permutation table
static unsigned char permutation[] = { 151,160,137,91,90,15,
	131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
	190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
	88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
	77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
	102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
	135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
	5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
	223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
	129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
	251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
	49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
	138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};


// gradients for 3d noise
static float g2[] = {
    1,2,
    1,-2,
    -1,2,
	-1,-2,
	2,1,
	2,-1,
	-2,1,
	-2,-1
};

// gradients for 3d noise
static float g3[] = {
    1,1,0,
    -1,1,0,
    1,-1,0,
    -1,-1,0,
    1,0,1,
    -1,0,1,
    1,0,-1,
    -1,0,-1, 
    0,1,1,
    0,-1,1,
    0,1,-1,
    0,-1,-1,
    1,1,0,
    0,-1,1,
    -1,1,0,
    0,-1,-1,
};

// gradients for 4D noise
static float g4[] = {
	0, -1, -1, -1,
	0, -1, -1, 1,
	0, -1, 1, -1,
	0, -1, 1, 1,
	0, 1, -1, -1,
	0, 1, -1, 1,
	0, 1, 1, -1,
	0, 1, 1, 1,
	-1, -1, 0, -1,
	-1, 1, 0, -1,
	1, -1, 0, -1,
	1, 1, 0, -1,
	-1, -1, 0, 1,
	-1, 1, 0, 1,
	1, -1, 0, 1,
	1, 1, 0, 1,
	
	-1, 0, -1, -1,
	1, 0, -1, -1,
	-1, 0, -1, 1,
	1, 0, -1, 1,
	-1, 0, 1, -1,
	1, 0, 1, -1,
	-1, 0, 1, 1,
	1, 0, 1, 1,
	0, -1, -1, 0,
	0, -1, -1, 0,
	0, -1, 1, 0,
	0, -1, 1, 0,
	0, 1, -1, 0,
	0, 1, -1, 0,
	0, 1, 1, 0,
	0, 1, 1, 0,
};

//-----------------------------------------------------------------------------

TerrainViewer::
TerrainViewer(const char* _title, int _width, int _height)
: TrackballViewer(_title, _width, _height)
{
  init();
}


//-----------------------------------------------------------------------------


void 
TerrainViewer::
init()
{
	// initialize parent
	TrackballViewer::init();

	// set camera to look at world coordinate center
	set_scene_pos(Vector3(0.0, 3.0, 3.0), 2.0);

	// load mesh shader
	m_terrainShader.create("procedural.vs", "procedural.fs");
	m_heightShader.create("terrain.vs", "terrain.fs");
	
	// load permutation table
	m_permutationTable.create(256,1,GL_RED, GL_RED, GL_UNSIGNED_BYTE, permutation);
	// make sure wrapping is correct
	m_permutationTable.bind();
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	m_permutationTable.unbind();
	
	// load 2D gradient table
	for(int i = 0; i < 8*2; i++) g2[i] /= sqrt(5.0);
	m_gradient2DTable.create(8,1,GL_RG32F, GL_RG, GL_FLOAT, g2);
	m_gradient2DTable.bind();
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	m_gradient2DTable.unbind();
	
	// load 3D gradient table
	for(int i = 0; i < 16*3; i++) g3[i] /= sqrt(2.0);
	m_gradient3DTable.create(16,1,GL_RGB32F_ARB, GL_RGB, GL_FLOAT, g3);
	m_gradient3DTable.bind();
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	m_gradient3DTable.unbind();
	
	// load 4D gradient table
	for(int i = 0; i < 32*4; i++) g4[i] /= sqrt(3.0);
	m_gradient4DTable.create(32,1,GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT, g4);
	m_gradient4DTable.bind();
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	m_gradient4DTable.unbind();
	
	// terrain textures
	m_lowest.create("data/lowest.tga");
	m_medium.create("data/medium.tga");
	m_high.create("data/high.tga");
	m_highest.create("data/highest.tga");
	
	m_fbo.create(MAP_X, MAP_Z, true);
	m_terrainOutputTexture.create(MAP_X,MAP_Z,GL_RGB32F_ARB,GL_RGB,GL_FLOAT);
	m_fbo.attachTexture(GL_COLOR_ATTACHMENT0_EXT, m_terrainOutputTexture.getID());
	
	first_draw = true;
	
}

//-----------------------------------------------------------------------------


void
TerrainViewer::
keyboard(int key, int x, int y)
{
	switch (key)
	{
		case 'h':
			printf("Help:\n");
			printf("'h'\t-\thelp\n");
			break;
		default:
			TrackballViewer::special(key, x, y);
			break;
	}
	
	glutPostRedisplay();
}

//-----------------------------------------------------------------------------


void
TerrainViewer::
special(int key, int x, int y)
{
	switch (key)
	{			
		default:
			TrackballViewer::special(key, x, y);
			break;
	}
	
	glutPostRedisplay();
}

//-----------------------------------------------------------------------------

void 
TerrainViewer::
renderFullScreenQuad()
{
	// render full screen quad (note that vertex coordinates are already in opengl coordinates, so no transformation required!)
	glBegin(GL_QUADS);
	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 0.0f);
	glVertex2f(-1.0f, -1.0f);
	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 0.0f);
	glVertex2f(1.0f, -1.0f);
	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 1.0f, 1.0f);
	glVertex2f(1.0f, 1.0f);
	glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0.0f, 1.0f);
	glVertex2f(-1.0f, 1.0f);
	glEnd();
}

//-----------------------------------------------------------------------------
/*
void TerrainViewer::draw_vertex(float (*terrain_normal)[MAP_Z][3], float (*terrain_height)[MAP_Z][3], int x, int z)
{
	//
	return;
}
*/

//-----------------------------------------------------------------------------

void TerrainViewer::make_normal(int x, int z)
{
	Vector3 p = Vector3(x/512., terrain_height[x][z][0], z/512.);
	Vector3 v1 = Vector3((x-1)/512., x > 0? terrain_height[x-1][z][0] : 0, z/512.)-p;
	Vector3 v2 = Vector3(x/512., z > 0? terrain_height[x][z-1][0] : 0, (z-1)/512.)-p;
	Vector3 v3 = Vector3((x+1)/512., x < MAP_X-1? terrain_height[x+1][z][0] : 0, z/512.)-p;
	Vector3 v4 = Vector3(x/512., z < MAP_Z-1? terrain_height[x][z+1][0] : 0, (z+1)/512.)-p;
	/*
	v1 = v1.normalize();
	v2 = v2.normalize();
	v3 = v3.normalize();
	v4 = v4.normalize();
	*/
	Vector3 n1 = v1.cross(v2).normalize();
	Vector3 n2 = v2.cross(v3).normalize();
	Vector3 n3 = v3.cross(v4).normalize();
	Vector3 n4 = v4.cross(v1).normalize();
	Vector3 n = -((n1+n2+n3+n4)/4).normalize();
	terrain_normal[x][z][0] = n.x;
	terrain_normal[x][z][1] = n.y;
	terrain_normal[x][z][2] = n.z;
}

//-----------------------------------------------------------------------------

void 
TerrainViewer::
draw_scene(DrawMode _draw_mode)
{
	if ( first_draw )
	{
		m_fbo.bind(GL_COLOR_ATTACHMENT0_EXT);
		m_terrainShader.bind();
		// clear screen
		glDisable(GL_DEPTH_TEST);
		
		m_permutationTable.setLayer(0);
		m_permutationTable.bind();
		m_gradient2DTable.setLayer(1);
		m_gradient3DTable.setLayer(1);
		m_gradient4DTable.setLayer(1);
		m_gradient3DTable.bind();
		m_terrainShader.setIntUniform("permSampler", m_permutationTable.getLayer());
		m_terrainShader.setIntUniform("gradSampler3d", m_gradient3DTable.getLayer());
		
		// render a quad over full image
		renderFullScreenQuad();
		
		m_permutationTable.unbind();
		m_gradient3DTable.unbind();	
		m_terrainShader.unbind();
		m_fbo.unbind();
		
		m_terrainOutputTexture.setLayer(3);
		m_terrainOutputTexture.bind();
		glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_FLOAT, terrain_height);
		m_terrainOutputTexture.unbind();
		
		// Compute normals
		fprintf(stderr, "%s %d\n", __FILE__, __LINE__);
		for(int x = 0; x < MAP_X; ++x) for(int z = 0; z < MAP_Z; ++z) {
			//fprintf(stderr, "%d %d\n", x, z);
			make_normal(x, z);
		}
		
		first_draw = false;
	}
	
	// clear screen
	glEnable(GL_DEPTH_TEST);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glDisable(GL_CULL_FACE);

	Object3D HeightTransformation;
	HeightTransformation.translateObject(Vector3(-1.5,-3.0,-1.0));
	HeightTransformation.scaleObject(Vector3(3.0,5.0,3.0));
	
	m_heightShader.bind();
	
	m_heightShader.setIntUniform("useTexture",1);
	m_lowest.setLayer(4);
	m_medium.setLayer(5);
	m_high.setLayer(6);
	m_highest.setLayer(7);
	m_terrainOutputTexture.bind();
	m_lowest.bind();
	m_medium.bind();
	m_high.bind();
	m_highest.bind();
	m_heightShader.setIntUniform("noise",m_terrainOutputTexture.getLayer());
	m_heightShader.setIntUniform("tLowest",m_lowest.getLayer());
	m_heightShader.setIntUniform("tMedium",m_medium.getLayer());
	m_heightShader.setIntUniform("tHigh",m_high.getLayer());
	m_heightShader.setIntUniform("tHighest",m_highest.getLayer());
	
	// set parameters
	m_heightShader.setMatrix4x4Uniform("worldcamera", m_camera.getTransformation().Inverse());
	m_heightShader.setMatrix4x4Uniform("projection", m_camera.getProjectionMatrix());
	m_heightShader.setMatrix4x4Uniform("modelworld", HeightTransformation.getTransformation());
	
	// Draw the terrain
	//glFrontFace(GL_CW);
	for(int x = 0; x < MAP_X-1; ++x) {
		glBegin(GL_TRIANGLE_STRIP);
		float X1, Y1, Z, X2, Y2;
		for(int z = 0; z < MAP_Z; ++z) {
			X1 = (float)x/MAP_X*2-1;
			Y1 = terrain_height[x][z][0];
			Z = (float)z/MAP_Z*2-1;
			X2 = (float)(x+1)/MAP_X*2-1;
			Y2 = terrain_height[x+1][z][0];
			
			glNormal3f(terrain_normal[x][z][0],
				terrain_normal[x][z][1],
				terrain_normal[x][z][2]);
				
			glVertex3f(X1, Y1, Z);
						
			glNormal3f(terrain_normal[x+1][z][0],
				terrain_normal[x+1][z][1],
				terrain_normal[x+1][z][2]);
				
			glVertex3f(X2, Y2, Z);
			//printf("%.3f %.3f %.3f\n", X, Y, Z);
		}
		glEnd();
	}
	
	// Stuff
	m_terrainOutputTexture.unbind();
	m_lowest.unbind();
	m_medium.unbind();
	m_high.unbind();
	m_highest.unbind();
	m_heightShader.unbind();
}


//=============================================================================
